﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
#if SILVERLIGHT
using System.Windows;
using System.Windows.Resources;
#endif
using XcoAppSpaces.Core;

namespace TypeResolve.Loader_Net45
{
    public static class AssemblyLoader
    {
	    public static void ConfigurePlatformBasedAssemblyLoading()
		{
#if !PocketPC && !SILVERLIGHT
			TypeUtils.LoadInitialAssembliesFunc = () => AppDomain.CurrentDomain.GetAssemblies().ToList();
#else
			TypeUtils.LoadInitialAssembliesFunc = TypeUtils.LoadCurrentAssemblies();
#endif
			TypeUtils.LoadCurrentAssembliesFunc = LoadCurrentAssemblies;
		    TypeUtils.AssembliesLoaded = false;
		    AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveEventHandler;
	    }

	    private static Assembly AssemblyResolveEventHandler(object sender, ResolveEventArgs args)
		{
			//this is e.g. called by the BinaryFormatter when an assembly should be resolved
			var shortName = GetShortAssemblyName(args.Name);
			var assembly = TypeUtils.CurrentAssemblies.FirstOrDefault(a => GetShortAssemblyName(a.FullName) == shortName);
			if (assembly == null && !TypeUtils.AssembliesLoaded)
			{
				TypeUtils.CurrentAssemblies = LoadCurrentAssemblies().ToList();
				assembly = TypeUtils.CurrentAssemblies.FirstOrDefault(a => GetShortAssemblyName(a.FullName) == shortName);
			}
			//if (assembly == null)
			//    log.Debug("AssemblyResolve: Assembly " + shortName + " not found!");
			//else
			//    log.Debug("AssemblyResolve: Resolved " + shortName);
			return assembly;
		}

		private static string GetShortAssemblyName(string fullName)
		{
			var idx = fullName.IndexOf(',');
			if (idx == -1)
				return fullName;
			return fullName.Substring(0, idx);
		}

#if !SILVERLIGHT
		public static List<Assembly> LoadCurrentAssemblies()
		{
			//Must search through whole directory because AppDomain.CurrentDomain.GetAssemblies() only returns assemblies that have already been loaded
			var assemblies = TypeUtils.CurrentAssemblies ?? new List<Assembly>();

#if !PocketPC
			string dir = Path.GetDirectoryName(typeof(TypeUtils).GetTypeInfo().Assembly.Location);
			//in asp.net projects, the assembly path is a temporary path somewhere under ...\temporary asp.net files\..., therefore another method for getting the directory must be used:
			if (dir == null || dir.IndexOf("asp.net", StringComparison.OrdinalIgnoreCase) > 0)
				dir = AppDomain.CurrentDomain.BaseDirectory;
#else
			string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
#endif
			LoadAssembliesFromPath(dir, assemblies);
			return assemblies;
		}

		private static void LoadAssembliesFromPath(string dir, List<Assembly> assemblies)
		{
			if (string.IsNullOrEmpty(dir))
				return;
			foreach (var file in Directory.GetFiles(dir, "*.dll").Concat(Directory.GetFiles(dir, "*.exe")))
			{
				try
				{
					var asm = Assembly.LoadFrom(file);
					if (!assemblies.Contains(asm))
					{
						assemblies.Add(asm);
						//log.Debug("Loaded Assembly: " + asm.FullName);
					}
				}
				catch (Exception) // ex)
				{
					//Console.WriteLine("Error loading assembly: " + ex.Message);
				}
			}
		}
#endif

#if !PocketPC && !SILVERLIGHT
		public static void LoadAdditionalAssemblies(string dir)
		{
			//allows loading additional assemblies (can be useful when not all assemblies of the application are placed in the same directory)
			//log.Debug("Loading assitional Assemblies from path: " + dir);
			var assemblies = TypeUtils.CurrentAssemblies.ToList();
			LoadAssembliesFromPath(dir, assemblies);
			TypeUtils.CurrentAssemblies = assemblies;
		}

		public static void LoadAdditionalAssemblies(Assembly assemblyInSamePath)
		{
			var dir = Path.GetDirectoryName(assemblyInSamePath.Location);
			LoadAdditionalAssemblies(dir);
		}
#endif

#if SILVERLIGHT
		public static IEnumerable<Assembly> LoadCurrentAssemblies()
		{
			if (assembliesLoaded)
				return currentAssemblies;
			
			var assemblies = new List<Assembly>();
			UIInvokeSync(() =>
			             	{
			             		//Deployment.Current.Parts can only be accessed from the UI thread??
			             		foreach (var part in System.Windows.Deployment.Current.Parts)
			             		{
			             			StreamResourceInfo info =Application.GetResourceStream(new Uri(part.Source, UriKind.Relative));
			             			Assembly assembly = new AssemblyPart().Load(info.Stream);
			             			assemblies.Add(assembly);
			             		}
			             	});

			assembliesLoaded = true;
			return assemblies;
		}

		private static void UIInvokeSync(Action a)
		{
			if (Deployment.Current.Dispatcher.CheckAccess())
			{
				a();  // meaning this is already the UI thread so just do it;
			}
			else
			{
				var e = new System.Threading.ManualResetEvent(false);
				Deployment.Current.Dispatcher.BeginInvoke(() => { a(); e.Set(); });
				e.WaitOne();
			}
		}
#endif
	}
}
